<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        function createTask(i, delay) {
            /*
                createTask其实就是创建一个函数，return的这个函数就是对异步请求的模拟，createTask返回的这个函数return一个promise，promise里是异步代码，异步代码内部使用resolve把异步结果传递给promise
            */
            return () => {
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        resolve(i);
                    }, delay);
                })
            }
        }
        class TaskQueue {
            constructor(max) {
                /*
                    max表示当前的还可以运行的任务数量，这是一个初始值，当然也就是最大限制并发的数量
                */
                this.max = max;
                this.taskList = [];

            }
            addTask(task) {
                this.taskList.push(task);
            }
            /*
                run方法就是取出任务队列taskList中的任务，让他们运行起来
            */
            run() {
                const length = this.taskList.length;
                if (!length) {
                    return;
                }
                /*
                    取出 min(当前可以运行的任务数量, 任务队列的长度)作为下面遍历的循环次数，其实这就是并发限制的核心逻辑：我如果直接遍历整个任务队列的长度那就等价于没有限制了
                */
                const min = Math.min(this.max, length);
                for (let i = 0; i < min; i++) {
                    /*
                        进入一次循环就相当于开始一个任务，更新任务队列taskList与max
                    */
                    const task = this.taskList.shift();
                    this.max--;
                    task().then(res => {
                        console.log(res);
                    }).catch((error) => {
                        console.log(error);
                    }).finally(() => {
                        /*
                            异步任务执行完最后更新max（返还一个执行空间），然后递归调用一次run方法，去唤醒任务队列（看看有没有任务需不需要继续执行）
                        */
                        this.max++;
                        this.run();
                    })
                }
            }
        }

        const taskQueue = new TaskQueue(3);
        // for (let i = 0; i < 20; i++) {
        //     const task = createTask(i);
        //     taskQueue.addTask(task);
        // }
        taskQueue.addTask(createTask(1, 3000));
        taskQueue.addTask(createTask(2, 2000));
        taskQueue.addTask(createTask(3, 1000));
        taskQueue.addTask(createTask(4, 1000));
        taskQueue.addTask(createTask(5, 2000));
        /*
            如果我们想让taskQueue自动调用run方法，我们可以在constructor里面加一个setTimeout,回调里调用this.run，因为添加任务是同步方法，这个setTimeout会在添加任务之后再执行
            这样就可以省略下面的手动调用
        */
        taskQueue.run();
    </script>
</body>

</html>